/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { ArkMethod, Stmt, ArkIfStmt, ArkClass } from "arkanalyzer/lib";
import Logger, { LOG_MODULE_TYPE } from 'arkanalyzer/lib/utils/logger';
import { BaseChecker, BaseMetaData } from "../BaseChecker";
import { Defects, IssueReport } from "../../model/Defects";
import { MatcherCallback, MatcherTypes, MethodMatcher, FileMatcher, ClassMatcher } from "../../matcher/Matchers";
import { Rule } from "../../model/Rule";
import { RuleListUtil } from "../../utils/common/DefectsList";

const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, 'MaxDepthCheck');
type Options = [{
    max: number;
}]
const MAX_DEPTH = 4;
export class MaxDepthCheck implements BaseChecker {
    metaData: BaseMetaData = {
        severity: 2,
        ruleDocPath: "docs/max-depth-check.md",
        description: `Max depth allowed is ${MAX_DEPTH}`
    };
    public rule: Rule;
    SIG_NAME: string = '';
    public defects: Defects[] = [];
    public issues: IssueReport[] = [];
    private defaultOptions: Options = [{ "max": MAX_DEPTH }];
    private fileMatcher: FileMatcher = {
        matcherType: MatcherTypes.FILE
    };
    private classMatcher: ClassMatcher = {
        matcherType: MatcherTypes.CLASS,
    };
    private methodMatcher: MethodMatcher = {
        matcherType: MatcherTypes.METHOD,
        file: [this.fileMatcher]
    };
    public registerMatchers(): MatcherCallback[] {
        const matchMethodCb: MatcherCallback = {
            matcher: this.methodMatcher,
            callback: this.check
        }
        const matchClassCb: MatcherCallback = {
            matcher: this.classMatcher,
            callback: this.check
        }
        return [matchMethodCb, matchClassCb];
    }

    public check = (targetMtd: ArkMethod | ArkClass) => {
        this.defaultOptions = this.rule && this.rule.option[0] ? this.rule.option as Options : this.defaultOptions;
        if (targetMtd instanceof ArkClass) {
            targetMtd.getMethods().forEach(method => {
                this.methodMatcher.name = [method.getName()];
                this.checkMethod(method);
            });
        } else {
            this.checkMethod(targetMtd);
        }
    };

    private checkMethod(arkMethod: ArkMethod) {
        if (!(arkMethod instanceof ArkMethod) || !arkMethod.getCode()) {
            return;
        }
        let currentDepth = 0;
        let lastText = '';
        let stmts = arkMethod.getBody()?.getCfg().getStmts() ?? [];
        const keywords = ['if', 'for', 'while', 'switch'];
        const keywordPattern = new RegExp(`^(${keywords.join('|')})\\b`);
        stmts = stmts.filter(stmt => (stmt instanceof ArkIfStmt && keywordPattern.test(stmt.getOriginalText() ?? '')));
        for (const stmt of stmts) {
            const text = stmt.getOriginalText() ?? '';
            if (!text) {
                continue;
            }
            // !lastText.includes('else ' + text)是由于底座把else if解析成if，所以需要排除这种情况
            if (!lastText || (lastText.includes(text) && !lastText.includes('else ' + text) && lastText !== text)) {
                currentDepth++;
            }
            if (currentDepth > (this.defaultOptions[0].max ?? MAX_DEPTH)) {
                this.metaData.description = `Blocks are nested too deeply (${currentDepth}). Maximum allowed is ${(this.defaultOptions[0].max ?? MAX_DEPTH)}.`;
                this.SIG_NAME = this.getControlFlowStmtString(text);
                this.addIssueReport(stmt);
            }
            lastText = text;
        }
    }

    private getControlFlowStmtString(line: string) {
        if (line.startsWith("for")) {
            return 'for';
        }
        if (line.startsWith("if")) {
            return 'if';
        }
        if (line.startsWith("while")) {
            return 'while';
        }
        if (line.startsWith("switch")) {
            return 'switch';
        }
        return '';
    }

    private addIssueReport(stmt: Stmt) {
        const severity = this.rule.alert ?? this.metaData.severity;  
        const warnInfo = this.getLineAndColumn(stmt);
        const fixKey = warnInfo.line + '%' + warnInfo.startCol + '%' + this.rule.ruleId;
        const mergeKey = warnInfo.filePath + '%' + fixKey + '%' + this.metaData.description;
        const defect = new Defects(warnInfo.line, warnInfo.startCol, this.metaData.description, severity, this.rule.ruleId,
            mergeKey, this.metaData.ruleDocPath, true, false, false, fixKey);
        this.issues.push(new IssueReport(defect, undefined));
        RuleListUtil.push(defect);
    }
    
    private getLineAndColumn(stmt: Stmt) {
        const originPosition = stmt.getOriginPositionInfo();
        const line = originPosition.getLineNo();
        const arkFile = stmt.getCfg()?.getDeclaringMethod().getDeclaringArkFile();
        if (arkFile) {
            const originText = stmt.getOriginalText() ?? '';
            let startCol = originPosition.getColNo();
            const pos = originText.indexOf(this.SIG_NAME);
            if (pos !== -1) {
                startCol += pos;
                const endCol = startCol + this.SIG_NAME.length - 1;
                const originPath = arkFile.getFilePath();
                return { line, startCol, endCol, filePath: originPath }
            }
        } else {
            logger.debug('arkFile is null');
        }
        return { line: -1, startCol: -1, endCol: -1, filePath: '' };
    }
}
